/**
 * Copyright (c) 2015-2017, Henry Yang 杨勇 (gismail@foxmail.com).
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.lambkit.service;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ClassUtil;
import com.jfinal.aop.Aop;
import com.jfinal.aop.AopManager;
import com.lambkit.dao.record.LambkitApiServiceFactory;
import com.lambkit.JFLambkit;
import com.lambkit.core.rpc.RpcConfig;

import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;

public class ServiceManager {

	private static final ServiceManager manager = new ServiceManager();

	public static ServiceManager me() {
		return manager;
	}

	private Map<String, ServiceObjectList> services;

	private BeanService beanService;

	// JFinal Aop
	private LambkitAopFactory aopFactory = new LambkitAopFactory();
	
	private LambkitApiServiceFactory apiServiceFactory;

	private ServiceManager() {
		AopManager.me().setAopFactory(aopFactory);
		apiServiceFactory = get(LambkitApiServiceFactory.class);
	}

	public Map<String, ServiceObjectList> getServices() {
		if (services == null) {
			services = MapUtil.newHashMap();
		}
		return services;
	}

	public <T> void put(ServiceObject service) {
		ServiceObjectList serviceObjectList = getServiceObjectList(service.getInterfaceClass());
		if (serviceObjectList == null) {
			serviceObjectList = new ServiceObjectList(service);
			getServices().put(service.getInterfaceClass().getName(), serviceObjectList);
		} else {
			serviceObjectList.addServiceObject(service);
		}
	}

	public ServiceObject getServiceObject(Class<?> interfaceClass) {
		ServiceObjectList serviceObjectList = getServiceObjectList(interfaceClass);
		if (serviceObjectList == null) {
			return null;
		} else {
			return serviceObjectList.getServiceObject();
		}
	}

	public ServiceObject getServiceObject(String interfaceClassName) {
		ServiceObjectList serviceObjectList = getServiceObjectList(interfaceClassName);
		if (serviceObjectList == null) {
			return null;
		} else {
			return serviceObjectList.getServiceObject();
		}
	}

	public ServiceObject getServiceObject(Class<?> interfaceClass, int type) {
		ServiceObjectList serviceObjectList = getServiceObjectList(interfaceClass);
		if (serviceObjectList == null) {
			return null;
		} else {
			return serviceObjectList.getServiceObject(type);
		}
	}

	public ServiceObject getServiceObject(String interfaceClassName, int type) {
		ServiceObjectList serviceObjectList = getServiceObjectList(interfaceClassName);
		if (serviceObjectList == null) {
			return null;
		} else {
			return serviceObjectList.getServiceObject(type);
		}
	}

	public ServiceObject getServiceObject(Class<?> interfaceClass, String code) {
		ServiceObjectList serviceObjectList = getServiceObjectList(interfaceClass);
		if (serviceObjectList == null) {
			return null;
		} else {
			return serviceObjectList.getServiceObject(code);
		}
	}

	public ServiceObject getServiceObject(String interfaceClassName, String code) {
		ServiceObjectList serviceObjectList = getServiceObjectList(interfaceClassName);
		if (serviceObjectList == null) {
			return null;
		} else {
			return serviceObjectList.getServiceObject(code);
		}
	}

	public ServiceObjectList getServiceObjectList(Class<?> interfaceClass) {
		return getServiceObjectList(interfaceClass.getName());
	}

	public ServiceObjectList getServiceObjectList(String interfaceClassName) {
		ServiceObjectList serviceObjectList = getServices().get(interfaceClassName);
		if(serviceObjectList==null) {
			for(ServiceObjectList serviceObjectList2 : getServices().values()) {
				List<ServiceObject> soList = serviceObjectList2.getServiceObjectList();
				for (ServiceObject serviceObject : soList) {
					boolean flag = serviceObject.hasClass(interfaceClassName);
					if(flag) {
						return serviceObjectList2;
					}
				}
			}//for
		}//if
		return serviceObjectList;
	}

	/**
	 * 获取一个非null的接口服务对象
	 * 
	 * @param interfaceClass
	 * @return
	 */
	public <T> ServiceObject getOrCreateServiceObject(Class<T> interfaceClass, Class<? extends T> implementClass) {
		ServiceObject service = getServiceObject(interfaceClass.getName());
		if (service == null) {
			service = new ServiceObject(interfaceClass, implementClass);
		}
		return service;
	}

	/**
	 * 设置接口服务的实现类
	 * 
	 * @param interfaceClass
	 * @param implementClass
	 */
	public <T> void setImpl(Class<T> interfaceClass, Class<? extends T> implementClass) {
		ServiceObject service = getServiceObject(interfaceClass.getName());
		if (service != null) {
			service.setImplementClass(implementClass);
		}
	}

	/**
	 * 设置接口服务的mock类
	 * 
	 * @param interfaceClass
	 * @param mockClass
	 */
	public <T> void setMock(Class<T> interfaceClass, Class<? extends T> mockClass) {
		ServiceObject service = getServiceObject(interfaceClass.getName());
		if (service != null) {
			service.setMockClass(mockClass);
		}
	}

	/**
	 * server端或者本地自用的接口服务注册
	 */
	public <T> void regist(Class<T> serviceClass) {
		ServiceObject service = new ServiceObject(serviceClass);
		put(service);
	}

	public <T> void regist(Class<T> serviceClass, Class<? extends T> implementClass) {
		ServiceObject service = new ServiceObject(serviceClass, implementClass);
		put(service);
	}

	public <T> void regist(Class<T> serviceClass, T instance)  {
		ServiceObject service = new ServiceObject(serviceClass, instance);
		put(service);
	}

	/**
	 * server端或者本地自用的接口服务注册
	 * 
	 * @param interfaceClass
	 * @param implementClass
	 * @param mockClass
	 */
	public <T> void mapping(Class<T> interfaceClass, Class<? extends T> implementClass, Class<? extends T> mockClass) {
		RpcConfig rpcConfig = JFLambkit.config(RpcConfig.class);
		mapping(interfaceClass, implementClass, mockClass, rpcConfig.getDefaultGroup(), rpcConfig.getDefaultVersion(),
				rpcConfig.getDefaultPort());
	}

	/**
	 * server端或者本地自用的接口服务注册
	 * 
	 * @param interfaceClass
	 * @param implementClass
	 * @param mockClass
	 * @param group
	 * @param version
	 * @param port
	 * @return
	 */
	public <T> boolean mapping(Class<T> interfaceClass, Class<? extends T> implementClass, Class<? extends T> mockClass, String group,
			String version, int port) {
		ServiceObject service = new ServiceObject(interfaceClass, implementClass, mockClass, group, version, port);
		put(service);
		if (implementClass != null && implementClass != interfaceClass) {
//			LambkitApi lambkitApi = interfaceClass.getAnnotation(LambkitApi.class);
//			if(lambkitApi==null) {
//				lambkitApi = implementClass.getAnnotation(LambkitApi.class);
//			}
//			if(lambkitApi!=null) {
//				return RpcKit.serviceExport(interfaceClass, service.instance(), group, version, port);
//			}
		}
		return false;
	}
	
	public <T> void mapping(Class<T> interfaceClass, Class<? extends Object> class1, Object mockClass, String group,
			String version, int port) {
		
		
	}

	/**
	 * rpc中client端的接口服务注册
	 * 
	 * @param interfaceClass
	 * @param mockClass
	 */
	public <T> void remote(Class<T> interfaceClass, Class<? extends T> mockClass) {
		RpcConfig rpcConfig = JFLambkit.config(RpcConfig.class);
		ServiceObject service = new ServiceObject(interfaceClass, null, mockClass, rpcConfig.getDefaultGroup(),
				rpcConfig.getDefaultVersion(), rpcConfig.getDefaultPort());
		service.setType(ServiceObject.CLIENT);
		put(service);
	}

	/**
	 * rpc中client端的接口服务注册
	 * 
	 * @param interfaceClass
	 * @param mockClass
	 * @param group
	 * @param version
	 * @param port
	 */
	public <T> void remote(Class<T> interfaceClass, Class<? extends T> mockClass, String group, String version, int port) {
		ServiceObject service = new ServiceObject(interfaceClass, null, mockClass, group, version, port);
		service.setType(ServiceObject.CLIENT);
		put(service);
	}

	/**
	 * 获取服务接口
	 * 
	 * @param interfaceClassName
	 * @return
	 */
	public <T> T get(String interfaceClassName) {
		ServiceObject service = getServiceObject(interfaceClassName);
		if (service != null) {
			return service.instance();
		}
		Class<T> clazz = ClassUtil.loadClass(interfaceClassName);
		if(isAbstract(clazz) || isInterface(clazz)) {
			return null;
		}
		return Aop.get(clazz);
	}

	/**
	 * 获取服务接口
	 * 
	 * @param interfaceClass
	 * @return
	 */
	public <T> T get(Class<T> interfaceClass) {
		ServiceObject service = getServiceObject(interfaceClass.getName());
		if (service != null) {
			return service.instance();
		}
//		System.out.println("ServiceManager: " + interfaceClass.getName()
//				+ " abstract: " + isAbstract(interfaceClass)
//				+ " interface: " + interfaceClass.isInterface());
		if(isAbstract(interfaceClass) || isInterface(interfaceClass)) {
			return null;
		}
		//System.out.println("ServiceManager AOP: " + interfaceClass.getName());
		return Aop.get(interfaceClass);
	}

	/**
	 * 是否抽象类
	 * @param clazz
	 * @return
	 */
	public boolean isAbstract(Class<?> clazz) {
		return Modifier.isAbstract(clazz.getModifiers());
	}

	/**
	 * 是否接口类
	 * @param clazz
	 * @return
	 */
	public boolean isInterface(Class<?> clazz) {
		return clazz.isInterface();
	}
	
	public <T> T singleton(Class<T> interfaceClass) {
		ServiceObject service = getServiceObject(interfaceClass.getName());
		if (service != null) {
			return service.singleton(aopFactory);
		}
		if(isAbstract(interfaceClass) || isInterface(interfaceClass)) {
			return null;
		}
		return Aop.get(interfaceClass);
	}
	
	/**
	 * 获取服务接口
	 * 
	 * @param interfaceClass
	 * @param defaultClass
	 * @return
	 */
	public <T> T get(Class<T> interfaceClass, Class<? extends T> defaultClass) {
		ServiceObject service = getServiceObject(interfaceClass.getName());
		if (service != null) {
			return service.instance();
		} else {
			return Aop.get(defaultClass);
		}
	}

	/**
	 * 只进行注入，对象自己创建 
	 * Service srv = Aop.inject(new Service()); 
	 * srv.doIt(); 
	 * 由于 Service对象是 new 出来的，不会被 AOP 代理，
	 * 所以其 doIt() 方法上的 Aaa 拦截器并不会生效。
	 * Aop.inject(...) 会对 OtherService otherSrv 进行注入，并且对 otherSrv 进行 AOP 代理， 
	 * 所以 OtherService.doOther() 方法上的 Bbb 拦截器会生效。
	 * @param targetObject
	 * @return
	 */
	public <T> T inject(T targetObject) {
		return Aop.inject(targetObject);
	}
	
	public <T> T getBean(Class<T> clazz) throws Exception {
		T targetObject = null;
		if(beanService!=null) {
			targetObject = beanService.getBean(clazz);
		}
		if(targetObject==null) {
			targetObject = get(clazz);
		}
		return targetObject;
	}

	public <T> Map<String, T> getBeansMapOfType(Class<T> clazz) throws Exception {
		Map<String, T> targetObject = null;
		if(beanService!=null) {
			targetObject = beanService.getBeansMapOfType(clazz);
		}
		if(targetObject==null) {
			ServiceObjectList serviceObjectList = getServiceObjectList(clazz);
			targetObject = serviceObjectList!=null ? serviceObjectList.getMap() : null;
		}
		return targetObject;
	}

	public <T> List<T> getBeansOfType(Class<T> type) throws Exception {
		List<T> targetObject = null;
		if(beanService!=null) {
			targetObject = beanService.getBeansOfType(type);
		}
		if(targetObject==null) {
			ServiceObjectList serviceObjectList = getServiceObjectList(type);
			targetObject = serviceObjectList!=null ? serviceObjectList.getList() : null;
		}
		return targetObject;
	}

	public BeanService getBeanService() {
		return beanService;
	}

	public void setBeanService(BeanService beanService) {
		this.beanService = beanService;
	}

	public LambkitApiServiceFactory getApiServiceFactory() {
		return apiServiceFactory;
	}

	public void setApiServiceFactory(LambkitApiServiceFactory apiServiceFactory) {
		this.apiServiceFactory = apiServiceFactory;
	}
}
